package movenment2copy;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import javax.swing.JPanel;

/**
 * The game-class, contains basically the entire snake-game.
 * @author Jonatan, Alexander.
 */
@SuppressWarnings("serial")
public class Game extends JPanel{
	
	private static int BORDERTHICKNESS = 5; // Thickness of the borders around the game.
	private static int WORMSIZE = 15; //Width of the worm/snake.
	private int SCOREFIELD = 40; // Height of the field below the game were the current score is.
	private int XMIN = BORDERTHICKNESS; // How far left the worm can travel.
	private int YMIN = BORDERTHICKNESS; // How far up the worm can travel.
	private static int CANVASWIDTH = (41*WORMSIZE)+(2*BORDERTHICKNESS); // The width of the actual snakefield and the borders, calculated after the size of the worm.
	private static int CANVASHEIGHT = (29*WORMSIZE)+(2*BORDERTHICKNESS); // The hright of the snakefield, borders and scorefield.
	private int XMAX = CANVASWIDTH-2*BORDERTHICKNESS; // How far Right the worm can travel.
	private int YMAX = CANVASHEIGHT-(SCOREFIELD); // How far down the worm can travel.
	
	private int DELAY = 100; //time between worm-moves in ms.
	private int ACCELERATION = 1; // How much faster the worm goes each time you eat an apple.
	private int DIRECTION; // Current direction.
	private int OLDDIRECTION; // Direction from the move before.
	private int appleX; // X-coordinate for the apple.
	private int appleY; // Y-coordinate for the apple.
	private int SCORE = 0; // The current score.
	private int MOVES = 0; // How many steps the worm has taken.
	
	private DeathListener deathListener; // For telling the JFrame when the worm is dead.
	private static CustomListener listener; // To listen for key-strokes.
	private LinkedList<Integer> wormX; // Holds the x-coordinates of the worm.
	private LinkedList<Integer> wormY; // Holds the y-coordinates of the worm.
	private LinkedList<Integer> levelX; // Holds the x-coordinates of the current level if any.
	private LinkedList<Integer> levelY; // Holds the y-coordinates of the current level if any.
	Graphics2D g2d; // Used for drawing.
	private boolean drawLevelTwo; //If leveling-text should be displayed.
	private boolean drawLevelThree; // If leveling-text should be displayed.
	private boolean initiateLevelTwo; // If level two has been initiated already.
	private boolean initiateLevelThree; // If level three has been initiated already.
	private int howManyTimes; // A variable to measure how many times something has been painted.
		
	public Game(){
		super();
		setSize(CANVASWIDTH+20, CANVASHEIGHT+20);
		setDoubleBuffered(true);
		setPreferredSize(new Dimension(CANVASWIDTH+20,CANVASHEIGHT+20));
					
	}
	
	/**
	 * Called to start the game.
	 */
	public void play(){
		this.setVisible(true);
		this.setFocusable(true);
		listener = new CustomListener();
		this.addKeyListener(listener);
		this.new GameThread(listener);
		DELAY = 100;
	}
	
	/**
	 * Thread to hold the game-loop.
	 * @author Jonatan
	 */
	private class GameThread extends Thread{
		
		private final CustomListener listener;
		
		public GameThread(CustomListener listener){
			this.listener = listener;
			start();
		}
		
		public void run(){
			SCORE = 0;
			MOVES = 0;
			wormX = new LinkedList<Integer>();
			wormY = new LinkedList<Integer>();
			DIRECTION = 39;
			OLDDIRECTION = 39;
			generateWorm();
			levelX = new LinkedList<Integer>();
			levelY = new LinkedList<Integer>();
			generateApple();
			drawLevelTwo = false;
			drawLevelThree = false;
			initiateLevelTwo = false;
			initiateLevelThree = false;
			howManyTimes = 0;

			boolean run = true;
			while(run){ // The "game-loop".
				try {
					Thread.sleep(DELAY); //The waiting time between worm-moves.
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				OLDDIRECTION = DIRECTION;
				int newDirection = listener.getLastKeyUsed();
				if((newDirection == 37 && OLDDIRECTION == 39) || (newDirection == 39 && OLDDIRECTION == 37) || (newDirection == 38 && OLDDIRECTION == 40) || (newDirection == 40 && OLDDIRECTION == 38)){
					DIRECTION = OLDDIRECTION;
				}else{
					DIRECTION = newDirection;
				}
				if(SCORE == 5 && initiateLevelTwo == false){
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) { 
						e.printStackTrace();
					}
					initiateLevelTwo = true;
				}
				if(SCORE == 10 && initiateLevelThree == false){
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) { 
						e.printStackTrace();
					}
					initiateLevelThree = true;
				}
				repaint();
				run = moveWorm();
				if(SCORE == 5 && initiateLevelTwo == false){
					generateLevelTwo();
				}else if(SCORE == 10 && initiateLevelThree == false){
					generateLevelThree();
				}
				
			}

			try {
				Thread.sleep(1000); //Sleep after the player has died so he can see what has happened.
			} catch (InterruptedException e) { 
				e.printStackTrace();
			}
			deathListener.hasDied();
			interrupt();
		}
	}
	
	public void paint(Graphics g) {
		super.paint(g);
        g2d = (Graphics2D) g;
        
		g2d.setPaint(Color.GRAY); // Draw the border
		g2d.fill(new Rectangle2D.Double(0, 0, CANVASWIDTH, CANVASHEIGHT));
		g2d.setPaint(Color.WHITE);
		g2d.fill(new Rectangle2D.Double(BORDERTHICKNESS, BORDERTHICKNESS, XMAX, YMAX));

		g2d.setPaint(Color.BLACK); // Draw the worm.
		Iterator<Integer> itY = wormY.iterator();
		Iterator<Integer> itX = wormX.iterator();		
		while(itX.hasNext() && itY.hasNext()){
			int tempX = itX.next();
			int tempY = itY.next();
			g2d.fill(new Rectangle2D.Double(tempX, tempY, WORMSIZE, WORMSIZE));
		}
        
		g2d.setPaint(Color.BLUE); // Draw the current level / obstacles.
		if(levelX != null){
			Iterator<Integer> itLX = levelX.iterator();
			Iterator<Integer> itLY = levelY.iterator();
			while(itLX.hasNext() && itLY.hasNext()){
				int tempX = itLX.next();
				int tempY = itLY.next();
				g2d.fill(new Rectangle2D.Double(tempX, tempY, WORMSIZE, WORMSIZE));
			}
		}
		
		g2d.setPaint(Color.RED);
		g2d.fill(new Rectangle2D.Double(appleX, appleY, WORMSIZE, WORMSIZE)); // Draw apple
		g2d.drawString("SCORE : "+SCORE, 40, YMAX+18); // Print score
		
		if(drawLevelTwo == true){ //If the player has just leveled, print out "LEVEL 2"
			g2d.drawString("LEVEL 2", XMAX/2, YMAX/2);
			howManyTimes++;
			if(howManyTimes > 5){
				drawLevelTwo = false;
			}			
		}
		if(drawLevelThree == true){ //If the player has just leveled again, print out "LEVEL 3"
			g2d.drawString("LEVEL 3", XMAX/2, YMAX/2);
			howManyTimes++;
			if(howManyTimes > 5){
				drawLevelThree = false;
			}			
		}
        g2d.dispose();
    }
	
	/**
	 * Put the level 2 obstacles in the level-variables.
	 */
	private void generateLevelTwo(){
		levelX.clear();
		levelY.clear();
		for(int i = 0; i*WORMSIZE < XMAX; i++){
			levelX.add((i*WORMSIZE)+BORDERTHICKNESS);
			levelY.add((0*WORMSIZE)+BORDERTHICKNESS);
		}
		for(int i = 0; i*WORMSIZE < XMAX; i++){
			levelX.add((i*WORMSIZE)+BORDERTHICKNESS);
			levelY.add((YMAX-WORMSIZE)+BORDERTHICKNESS);
		}
		for(int i = 0; i*WORMSIZE < YMAX; i++){
			levelX.add(BORDERTHICKNESS);
			levelY.add((i*WORMSIZE)+BORDERTHICKNESS);
		}
		for(int i = 0; i*WORMSIZE < YMAX; i++){
			levelX.add((XMAX-WORMSIZE)+BORDERTHICKNESS);
			levelY.add((i*WORMSIZE)+BORDERTHICKNESS);
		}
		generateWorm();
		DIRECTION = 39;
		OLDDIRECTION = 39;
		generateApple();
		drawLevelTwo = true;
		howManyTimes = 0;
		DELAY = 100;
	}
	
	/**
	 * Put level 3 obstacles in the level-variables.
	 */
	public void generateLevelThree(){
		levelX.clear();
		levelY.clear();
		for(int i = 3; i < 12; i++){
			levelX.add((8*WORMSIZE)+BORDERTHICKNESS);
			levelY.add((i*WORMSIZE)+BORDERTHICKNESS);
			levelX.add((32*WORMSIZE)+BORDERTHICKNESS);
			levelY.add((i*WORMSIZE)+BORDERTHICKNESS);
		}
		for(int i = 15; i < 24; i++){
			levelX.add((8*WORMSIZE)+BORDERTHICKNESS);
			levelY.add((i*WORMSIZE)+BORDERTHICKNESS);
			levelX.add((32*WORMSIZE)+BORDERTHICKNESS);
			levelY.add((i*WORMSIZE)+BORDERTHICKNESS);
		}
		generateWorm();
		DIRECTION = 39;
		OLDDIRECTION = 39;
		generateApple();
		drawLevelThree = true;
		howManyTimes = 0;
		DELAY = 100;
	}
	
	/**
	 * Moves the worm one step in the last valid direction typed.
	 * Checks for crashes with walls, the snake itself and apples.
	 * @return
	 */
	private boolean moveWorm(){
		MOVES++;
		int headX = wormX.getFirst();
		int headY = wormY.getFirst();
				
		if(DIRECTION == 37){
			headX -=WORMSIZE;
		}else if(DIRECTION == 38){
			headY -=WORMSIZE;
		}else if(DIRECTION == 39){
			headX +=WORMSIZE;
		}else if(DIRECTION == 40){
			headY +=WORMSIZE;
		}
		if(headX < XMIN || headY < YMIN || headX > XMAX || headY > YMAX){
			//return false;
			if(headX < XMIN){
				headX = XMAX-2*BORDERTHICKNESS;
			}else if(headY < YMIN){
				headY = YMAX-2*BORDERTHICKNESS;
			}else if(headX > XMAX){
				headX = XMIN;
			}else if(headY > YMAX){
				headY = YMIN;
			}
		}
		
		if(checkForCrash(headX, headY) == true){
			if(MOVES > 1){
				return false;
			}
		}
		
		wormX.addFirst(headX);
		wormY.addFirst(headY);

		if(headX != appleX || headY != appleY){
			wormX.removeLast();
			wormY.removeLast();
		}else{
			SCORE++;
			generateApple();
		}
		
		return true;
	}
	
	/**
	 * Checks if the pair of coordinates in the parameters match a pair of coordinates in the worm.
	 * @param x-coordinate.
	 * @param y-coordinate.
	 * @return true if crash.
	 */
	private boolean checkForCrash(int x, int y){
		Iterator<Integer> itX = wormX.iterator();
		Iterator<Integer> itY = wormY.iterator();
		while(itX.hasNext() && itY.hasNext()){
			int tempX = itX.next();
			int tempY = itY.next();
			if(x == tempX && y == tempY){
				return true;
			}
		}
				
		Iterator<Integer> itLX = levelX.iterator();
		Iterator<Integer> itLY = levelY.iterator();
		while(itLX.hasNext() && itLY.hasNext()){
			int tempX = itLX.next();
			int tempY = itLY.next();
			if(x == tempX && y == tempY){
				return true;
			}
		}
		
		return false;
	}
	
	/**
     * Waits for a specified number of milliseconds before finishing.
     * This provides an easy way to specify a small delay which can be
     * used when producing animations.
     * @param  milliseconds  the number 
     */
    /*public void wait(int milliseconds)
    {
        try
        {
            Thread.sleep(milliseconds);
        } 
        catch (InterruptedException e)
        {
            // ignoring exception at the moment
        }
    }*/
    
	/**
	 * Generates a worm so the player has a worm to start with.
	 * Basically adds coordinates to the worm-variables.
	 */
	private void generateWorm(){
		wormX.clear();
		wormY.clear();
		wormX.add((3*WORMSIZE)+BORDERTHICKNESS);
		wormY.add((13*WORMSIZE)+BORDERTHICKNESS);
		
		wormX.add((2*WORMSIZE)+BORDERTHICKNESS);
		wormY.add((13*WORMSIZE)+BORDERTHICKNESS);
		
		wormX.add((1*WORMSIZE)+BORDERTHICKNESS);
		wormY.add((13*WORMSIZE)+BORDERTHICKNESS);
	}
	
	
	/**
	 * Generates coordinates for an apple.
	 * Check so that the apple doesn't collide with anything so that snake can eat it.
	 */
	private void generateApple(){
		Random random = new Random();
		do{
			appleX = XMIN + WORMSIZE*random.nextInt(XMAX/WORMSIZE);
			appleY = YMIN + WORMSIZE*random.nextInt(YMAX/WORMSIZE);
			//apple = new Coord(XMIN + WORMSIZE*random.nextInt(XMAX/WORMSIZE), YMIN + WORMSIZE*random.nextInt(YMAX/WORMSIZE));
		}while(checkForCrash(appleX, appleY) == true);
		DELAY -= ACCELERATION;
	}
	
	/**
	 * Return the highscore.
	 * @return SCORE.
	 */
	public int getHighScore(){
		return SCORE;
	}
	
	/**
	 * Set a deathListener to listen for when the player has died.
	 * @param deathListener.
	 */
	public void setDeathListener(DeathListener deathListener) {
		this.deathListener = deathListener;
	}

}
